package com.tnovoselec.android.ui.drawable;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;

/**
 * A Drawable object that draws primitive shapes. A ShapeDrawable takes a
 * {@link android.graphics.drawable.shapes.Shape} object and manages its
 * presence on the screen. If no Shape is given, then the ShapeDrawable will
 * default to a {@link android.graphics.drawable.shapes.RectShape}.
 * 
 * <p>
 * This object can be defined in an XML file with the <code>&lt;shape></code>
 * element.
 * </p>
 * 
 * 
 * @attr ref android.R.styleable#ShapeDrawablePadding_left
 * @attr ref android.R.styleable#ShapeDrawablePadding_top
 * @attr ref android.R.styleable#ShapeDrawablePadding_right
 * @attr ref android.R.styleable#ShapeDrawablePadding_bottom
 * @attr ref android.R.styleable#ShapeDrawable_color
 * @attr ref android.R.styleable#ShapeDrawable_width
 * @attr ref android.R.styleable#ShapeDrawable_height
 */
public class ProgressDrawable extends Drawable {

    private ProgressState mProgressState;
    private boolean mMutated;

    /**
     * ShapeDrawable constructor.
     */
    public ProgressDrawable() {
	this((ProgressState) null);
	mProgressState.mBytesCurrent = 0;
	mProgressState.mBytesTotal = 0;
    }

    /**
     * Creates a ShapeDrawable with a specified Shape.
     * 
     * @param s
     *            the Shape that this ShapeDrawable should be
     */
    public ProgressDrawable(long bytesCurrent, long bytesTotal) {
	this((ProgressState) null);

	mProgressState.mBytesCurrent = bytesCurrent;
	mProgressState.mBytesTotal = bytesTotal;
    }

    private ProgressDrawable(ProgressState state) {
	mProgressState = new ProgressState(state);
    }

    public long getBytesCurrent() {
	return mProgressState.mBytesCurrent;
    }

    public void setBytesCurrent(long bytesCurrent) {
	mProgressState.mBytesCurrent = bytesCurrent;
	updateDrawable();
    }

    public long getBytesTotal() {
	return mProgressState.mBytesTotal;
    }

    public void setBytesTotal(long bytesTotal) {
	mProgressState.mBytesTotal = bytesTotal;
	updateDrawable();
    }

    /**
     * Returns the Paint used to draw the shape.
     */
    public Paint getFillPaint() {
	return mProgressState.mFillPaint;
    }

    public Paint getTextPaint() {
	return mProgressState.mTextPaint;
    }

    /**
     * Sets the intrinsic (default) width for this shape.
     * 
     * @param width
     *            the intrinsic width (in pixels)
     */
    public void setIntrinsicWidth(int width) {
	mProgressState.mIntrinsicWidth = width;
	invalidateSelf();
    }

    /**
     * Sets the intrinsic (default) height for this shape.
     * 
     * @param height
     *            the intrinsic height (in pixels)
     */
    public void setIntrinsicHeight(int height) {
	mProgressState.mIntrinsicHeight = height;
	invalidateSelf();
    }

    @Override
    public int getIntrinsicWidth() {
	return mProgressState.mIntrinsicWidth;
    }

    @Override
    public int getIntrinsicHeight() {
	return mProgressState.mIntrinsicHeight;
    }

    private static int modulateAlpha(int paintAlpha, int alpha) {
	int scale = alpha + (alpha >>> 7); // convert to 0..256
	return paintAlpha * scale >>> 8;
    }

    @Override
    public void draw(Canvas canvas) {
	Rect r = getBounds();
	Paint paint = mProgressState.mFillPaint;

	int prevAlpha = paint.getAlpha();
	paint.setAlpha(modulateAlpha(prevAlpha, mProgressState.mAlpha));

	int count = canvas.save();
	canvas.translate(r.left, r.top);

	drawProgress(canvas, mProgressState.mFillPaint, mProgressState.mTextPaint, mProgressState.mBytesCurrent, mProgressState.mBytesTotal);

	canvas.restoreToCount(count);

	paint.setAlpha(prevAlpha);
    }

    private void drawProgress(Canvas canvas, Paint fillPaint, Paint textPaint, long mBytesCurrent, long mBytesTotal) {
	final float ratio = (float) mProgressState.mBytesCurrent / mProgressState.mBytesTotal;
	final float sweepAngle = ratio * 360f;
	final RectF rect = new RectF(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
	final Paint p2 = new Paint(fillPaint);
	p2.setAlpha(128);
	textPaint.setTextAlign(Align.CENTER);

	canvas.drawArc(rect, 0, sweepAngle, true, fillPaint);
	canvas.drawOval(rect, p2);
	canvas.drawText(Math.round(ratio * 100) + "%", rect.centerX(), rect.centerY() + textPaint.getTextSize() / 2, textPaint);
    }

    @Override
    public int getChangingConfigurations() {
	return super.getChangingConfigurations() | mProgressState.mChangingConfigurations;
    }

    /**
     * Set the alpha level for this drawable [0..255]. Note that this drawable
     * also has a color in its paint, which has an alpha as well. These two
     * values are automatically combined during drawing. Thus if the color's
     * alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then
     * the combined alpha that will be used during drawing will be 37.5% (i.e.
     * 96).
     */
    @Override
    public void setAlpha(int alpha) {
	mProgressState.mAlpha = alpha;
	invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
	mProgressState.mFillPaint.setColorFilter(cf);
	invalidateSelf();
    }

    @Override
    public int getOpacity() {
	return PixelFormat.TRANSLUCENT;
    }

    @Override
    public void setDither(boolean dither) {
	mProgressState.mFillPaint.setDither(dither);
	invalidateSelf();
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
	super.onBoundsChange(bounds);
	updateDrawable();
    }

    private void updateDrawable() {
	final Rect r = getBounds();
	setIntrinsicWidth(r.width());
	setIntrinsicHeight(r.height());
	invalidateSelf();
    }

    @Override
    public ConstantState getConstantState() {
	mProgressState.mChangingConfigurations = getChangingConfigurations();
	return mProgressState;
    }

    @Override
    public Drawable mutate() {
	if (!mMutated && super.mutate() == this) {
	    mProgressState.mFillPaint = new Paint(mProgressState.mFillPaint);
	    mProgressState.mTextPaint = new Paint(mProgressState.mTextPaint);
	    mProgressState.mBytesCurrent = mProgressState.mBytesCurrent;
	    mProgressState.mBytesTotal = mProgressState.mBytesTotal;
	    mMutated = true;
	}
	return this;
    }

    /**
     * Defines the intrinsic properties of this ShapeDrawable's Shape.
     */
    final static class ProgressState extends ConstantState {
	int mChangingConfigurations;
	Paint mFillPaint;
	Paint mTextPaint;
	long mBytesCurrent;
	long mBytesTotal;
	int mIntrinsicWidth;
	int mIntrinsicHeight;
	int mAlpha = 255;

	ProgressState(ProgressState orig) {
	    if (orig != null) {
		mFillPaint = orig.mFillPaint;
		mTextPaint = orig.mTextPaint;
		mBytesCurrent = orig.mBytesCurrent;
		mBytesTotal = orig.mBytesTotal;
		mIntrinsicWidth = orig.mIntrinsicWidth;
		mIntrinsicHeight = orig.mIntrinsicHeight;
		mAlpha = orig.mAlpha;
	    } else {
		mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mFillPaint.setStyle(Style.FILL);
		mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mBytesCurrent = 0;
		mBytesTotal = 0;
	    }
	}

	@Override
	public Drawable newDrawable() {
	    return new ProgressDrawable(this);
	}

	@Override
	public Drawable newDrawable(Resources res) {
	    return new ProgressDrawable(this);
	}

	@Override
	public int getChangingConfigurations() {
	    return mChangingConfigurations;
	}
    }

}
